using System;

namespace ADEVSO.Collections
{
    public class Redim
    {
        /// <summary>
        /// Change the size of one or more dimensions of an array that has already been formally declared
        /// Type is equals index sub zero value of original array
        /// Preserve is false by default
        /// </summary>
        /// <param name="source"></param>
        /// <param name="lengths"></param>
        /// <returns></returns>
        public static Array RedimArray(Array source, int[] lengths)
        {
            return RedimArray(source, lengths, false, source.GetValue(new int[lengths.Length]).GetType());
        }

        /// <summary>
        /// Change the size of one or more dimensions of an array that has already been formally declared
        /// Type is equals index sub zero value of original array
        /// </summary>
        /// <param name="array"></param>
        /// <param name="lengths"></param>
        /// <param name="preserve"></param>
        /// <returns></returns>
        public static Array RedimArray(Array source, int[] lengths, bool preserve)
        {
            return RedimArray(source, lengths, preserve, source.GetValue(new int[lengths.Length]).GetType());
        }

        /// <summary>
        /// Change the size of one or more dimensions of an array that has already been formally declared
        /// Preserve is true by default
        /// </summary>
        /// <param name="source"></param>
        /// <param name="lengths"></param>
        /// <param name="type"></param>
        /// <returns></returns>
        public static Array RedimArray(Array source, int[] lengths, Type type)
        {
            return RedimArray(source, lengths, true, type);
        }

        /// <summary>
        /// Change the size of one or more dimensions of an array that has already been formally declared
        /// </summary>
        /// <param name="source"></param>
        /// <param name="lengths"></param>
        /// <param name="preserve"></param>
        /// <param name="type"></param>
        /// <returns></returns>
        public static Array RedimArray(Array source, int[] lengths, bool preserve, Type type)
        {
            Array result = Array.CreateInstance(type, lengths);

            if (preserve)
            {
                // Validate dimension magnitudes for preserve
                if (ValidateDimensions(source, lengths))
                {
                    int lastDim = lengths.Length - 1;
                    int[] preserveLengths = lengths;
                    preserveLengths[lastDim] = Math.Min(source.GetLength(lastDim), lengths[lastDim]);
                    PreserveArray(result, source, type, preserveLengths, 0, new int[lengths.Length]);
                }
                else
                {
                    throw new Exception("Cannot resize this dimension with preserve option");
                }
            }
            return result;
        }

        /// <summary>
        /// Preserve Array Source Info
        /// </summary>
        /// <param name="target"></param>
        /// <param name="source"></param>
        /// <param name="type"></param>
        /// <param name="lengths"></param>
        /// <param name="level"></param>
        /// <param name="currentIndexes"></param>
        private static void PreserveArray(Array target, Array source, Type type, int[] lengths, int level, int[] currentIndexes)
        {
            // Assuming lengths last magnitude is the minimum between the original and the new array
            for (int i = 0; i < lengths[level]; i++)
            {
                currentIndexes[level] = i;
                if (level == lengths.Length - 1)
                    target.SetValue(Convert.ChangeType(source.GetValue(currentIndexes), type), currentIndexes);
                else
                    PreserveArray(target, source, type, lengths, level + 1, currentIndexes);
            }
        }

        /// <summary>
        /// Validate new Dimensions are equals original except last
        /// </summary>
        /// <param name="source"></param>
        /// <param name="lengths"></param>
        /// <returns></returns>
        private static bool ValidateDimensions(Array source, int[] lengths)
        {
            bool result = (source.Rank.Equals(lengths.Length)) ? true : false;
            if (result)
            {
                for (int i = 0; i < lengths.Length - 2; i++)
                {
                    if (!source.GetLength(i).Equals(lengths.GetValue(i)))
                    {
                        result = false;
                        break;
                    }
                }
            }
            return result;
        }
    }
}
